package cn.acheng1314.im.socket.coder;

import cn.acheng1314.im.socket.model.PongMessage;
import cn.acheng1314.im.common.model.SentBody;
import cn.acheng1314.im.socket.constant.ImConstant;
import cn.acheng1314.im.socket.constant.ProtobufType;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * TLV编码通信协议设计<br/>
 * 参照：http://www.wtango.com/tlv%E7%BC%96%E7%A0%81%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AE%E8%AE%BE%E8%AE%A1/
 */
public class SocketMessageDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
        /*
         * 消息体不足3位，发生断包情况
         */
        if (buffer.readableBytes() < ImConstant.DATA_HEADER_LENGTH) {
            return;
        }

        //标记缓冲区的readerIndex
        buffer.markReaderIndex();

        //读取缓冲区前三个字节，然后转换为标准通信格式的表达，获取对应消息传输的正确长度
        byte type = buffer.readByte();

        byte lv = buffer.readByte();
        byte hv = buffer.readByte();
        int length = getContentLength(lv, hv);

        /*
         * 发生断包情况，等待接收完成
         */
        if (buffer.readableBytes() < length) {
            buffer.resetReaderIndex();
            return;
        }

        byte[] dataBytes = new byte[length];
        buffer.readBytes(dataBytes);

        Object message = convertMessageObject(dataBytes, type);

        out.add(message);
    }

    public Object convertMessageObject(byte[] data, byte type) throws Exception {
        if (ProtobufType.Pong == type) {
            return PongMessage.getInstance();
        }

        cn.acheng1314.im.common.model.proto.sent.Model bodyProto = cn.acheng1314.im.common.model.proto.sent.Model.parseFrom(data);
        SentBody body = new SentBody();
        body.setKey(bodyProto.getKey());
        body.setTimestamp(bodyProto.getTimestamp());
        body.putAll(bodyProto.getDataMap());
        return body;
    }


    /**
     * 解析消息体长度
     * 最大消息长度为2个字节表示的长度，即为65535
     *
     * @param lv 低位1字节消息长度
     * @param hv 高位1字节消息长度
     * @return 消息的真实长度
     */
    private int getContentLength(byte lv, byte hv) {
        //Java 总是把 byte 当做有符处理；我们可以通过将其和 0xFF 进行二进制与得到它的无符值
        int l = (lv & 0xff);
        int h = (hv & 0xff);
        return l | h << 8;
    }
}
